#!/bin/bash

# set -euo pipefail

# git_pull_all - 批量拉取Git仓库代码
#
# 用途: 批量拉取指定目录下所有Git仓库的最新代码
# 作者: Claude
# 版本: 1.3

usage() {
    cat << EOF
git_pull_all - 批量拉取Git仓库代码

用法: $0 [目录] [选项]

描述:
    批量拉取指定目录下所有Git仓库的最新代码
    支持main/master分支检测，提供详细的执行统计和错误处理

参数:
    目录              要扫描的目录路径 (默认: 当前目录)
                    如果不指定，则扫描当前目录

选项:
    -h, --help       显示此帮助信息
    -v, --verbose    显示详细输出，包括调试信息
    -q, --quiet      静默模式，只显示错误信息
    --dry-run       只显示将要执行的操作，不实际执行git pull
    --stash         自动贮藏本地修改后再拉取（谨慎使用）
    --reset-hard    自动重置本地修改后再拉取（谨慎使用，会丢失本地修改！）

功能特性:
    - 自动检测Git仓库（查找.git文件夹）
    - 优先拉取main分支，其次master分支
    - 详细的错误处理和诊断信息
    - 完整的执行统计报告
    - 支持干运行模式预览操作
    - 详细的日志记录

使用示例:
    $0                              # 扫描当前目录下的所有Git仓库
    $0 /path/to/projects            # 扫描指定目录下的所有Git仓库
    $0 ~/workspace -v              # 扫描home目录并显示详细信息
    $0 ~/projects --dry-run        # 预览将要执行的操作，不实际执行
    $0 /home/user/code -q         # 静默模式执行，只显示错误

退出代码:
    0  成功执行
    1  执行过程中发生错误
    2  参数错误或目录不存在
    3  未发现Git仓库

日志文件:
    详细的执行日志将保存在 /tmp/git_pull_all_YYYYMMDD_HHMMSS.log

常见问题:
    1. 权限错误: 确保对目标目录有读取权限
    2. 网络错误: 检查网络连接和远程仓库访问权限
    3. 认证错误: 确保SSH密钥配置正确或Git凭据有效
    4. 仓库状态: 确保没有未提交的更改或合并冲突
EOF
}

log() {
    local level="$1"
    shift
    local message="$*"
    local timestamp
    timestamp=$(date '+%Y-%m-%d %H:%M:%S')
    
    if [[ "${VERBOSE:-false}" == "true" ]] || [[ "${level}" == "ERROR" ]]; then
        echo "[${timestamp}] [${level}] ${message}" | tee -a "${LOG_FILE}"
    fi
}

error() {
    log "ERROR" "$1" >&2
    exit 1
}

# 安全地执行函数并处理错误
safe_execute() {
    local func_name="$1"
    shift
    if ! "$func_name" "$@"; then
        return 1
    fi
    return 0
}

main() {
    local verbose=false
    local quiet=false
    local dry_run=false
    local stash_changes=false
    local reset_hard=false
    local target_dir="."
    local -a git_repos=()
    local overall_exit_code=0
    local SCRIPT_DIR
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
    readonly SCRIPT_DIR
    local LOG_FILE
    LOG_FILE="/tmp/git_pull_all_$(date +%Y%m%d_%H%M%S).log"
    readonly LOG_FILE
    
    # 解析命令行参数
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                usage
                exit 0
                ;;
            -v|--verbose)
                verbose=true
                VERBOSE=true
                shift
                ;;
            -q|--quiet)
                quiet=true
                shift
                ;;
            --dry-run)
                dry_run=true
                shift
                ;;
            --stash)
                stash_changes=true
                shift
                ;;
            --reset-hard)
                reset_hard=true
                shift
                ;;
            -*)
                error "未知选项: $1"
                ;;
            *)
                if [[ "${target_dir}" == "." ]]; then
                    target_dir="$1"
                else
                    error "多个目录参数: $1"
                fi
                shift
                ;;
        esac
    done
    
    # 验证目标目录
    if [[ ! -d "${target_dir}" ]]; then
        error "目录不存在: ${target_dir}"
    fi
    
    target_dir="$(cd "${target_dir}" && pwd)"
    log "INFO" "开始扫描目录: ${target_dir}"
    
    if [[ "${dry_run}" == "true" ]]; then
        log "INFO" "干运行模式 - 不实际执行git pull操作"
    fi
    
    if [[ "${stash_changes}" == "true" ]]; then
        log "INFO" "自动贮藏模式 - 将自动贮藏本地修改"
    fi
    
    if [[ "${reset_hard}" == "true" ]]; then
        log "WARN" "强制重置模式 - 将丢弃所有本地修改！"
    fi
    
    # 遍历目录，查找Git仓库
    log "INFO" "开始扫描Git仓库..."
    
    # 获取所有子目录
    local -a subdirs=()
    local traverse_output
    traverse_output=$(traverse_directories "${target_dir}") || {
        log "ERROR" "未发现任何子目录: ${target_dir}"
        exit 1
    }
    readarray -t subdirs <<< "${traverse_output}"
    
    # 检查每个子目录是否为Git仓库
    for subdir in "${subdirs[@]}"; do
        local repo_path="${target_dir}/${subdir}"
        
        if safe_execute detect_git_repository "${repo_path}"; then
            git_repos+=("${repo_path}")
            log "INFO" "发现Git仓库: ${subdir}"
        else
            log "DEBUG" "跳过非Git目录: ${subdir}"
        fi
    done
    
    if [[ ${#git_repos[@]} -eq 0 ]]; then
        log "ERROR" "未发现任何Git仓库"
        exit 1
    fi
    
    log "INFO" "发现 ${#git_repos[@]} 个Git仓库"
    
    # 执行Git Pull操作
    local success_count=0
    local failure_count=0
    local total_repos=${#git_repos[@]}
    local current_repo=0
    
    for repo in "${git_repos[@]}"; do
        current_repo=$((current_repo + 1))
        local repo_name
        repo_name=$(basename "${repo}")
        
        if [[ "${dry_run}" == "true" ]]; then
            if [[ "${VERBOSE:-false}" == "true" ]]; then
                log "INFO" "[干运行] 检查仓库 [${current_repo}/${total_repos}]: ${repo_name}"
            fi
            
            if safe_execute execute_git_pull "${repo}" "true" "${stash_changes}" "${reset_hard}"; then
                if [[ "${VERBOSE:-false}" == "true" ]]; then
                    log "INFO" "[干运行] 将成功拉取: ${repo_name}"
                fi
                success_count=$((success_count + 1))
            else
                if [[ "${VERBOSE:-false}" == "true" ]]; then
                    log "INFO" "[干运行] 将跳过: ${repo_name}"
                fi
                failure_count=$((failure_count + 1))
            fi
        else
            if [[ "${VERBOSE:-false}" == "true" ]]; then
                log "INFO" "开始处理 [${current_repo}/${total_repos}]: ${repo_name}"
            fi
            
            if safe_execute execute_git_pull "${repo}" "false" "${stash_changes}" "${reset_hard}"; then
                success_count=$((success_count + 1))
            else
                failure_count=$((failure_count + 1))
                overall_exit_code=1
            fi
        fi
    done
    
    # 生成统计报告
    if [[ "${dry_run}" == "false" ]]; then
        log "INFO" "执行完成，生成统计报告..."
        generate_statistics "${git_repos[@]}"
    fi
    
    # 显示最终统计
    log "INFO" "========== 最终结果 =========="
    log "INFO" "总仓库数: ${#git_repos[@]}"
    log "INFO" "成功: ${success_count}"
    log "INFO" "失败: ${failure_count}"
    
    if [[ "${dry_run}" == "true" ]]; then
        log "INFO" "模式: 干运行"
    elif [[ "${stash_changes}" == "true" ]]; then
        log "INFO" "模式: 自动贮藏"
    elif [[ "${reset_hard}" == "true" ]]; then
        log "WARN" "模式: 强制重置（可能丢失数据！）"
    else
        log "INFO" "模式: 标准执行"
    fi
    log "INFO" "============================"
    
    exit "${overall_exit_code}"
}

# 遍历指定目录的直接子目录
traverse_directories() {
    local target_dir="$1"
    local -a subdirs=()
    
    log "INFO" "开始遍历目录: ${target_dir}"
    
    if [[ ! -d "${target_dir}" ]]; then
        error "目录不存在: ${target_dir}"
    fi
    
    for dir in "${target_dir}"/*; do
        [[ -d "${dir}" ]] || continue
        
        local relative_dir="${dir#"${target_dir}"/}"
        [[ "${relative_dir}" != "${target_dir}" ]] || continue
        
        subdirs+=("${relative_dir}")
        log "DEBUG" "发现子目录: ${relative_dir}"
    done
    
    if [[ ${#subdirs[@]} -eq 0 ]]; then
        log "INFO" "在 ${target_dir} 中未发现子目录"
        return 1
    fi
    
    log "INFO" "找到 ${#subdirs[@]} 个子目录"
    printf '%s\n' "${subdirs[@]}"
    return 0
}

# 检测是否为 Git 仓库
detect_git_repository() {
    local repo_path="$1"
    
    if [[ ! -d "${repo_path}" ]]; then
        log "ERROR" "路径不存在: ${repo_path}"
        return 1
    fi
    
    if [[ -d "${repo_path}/.git" ]]; then
        log "DEBUG" "发现 Git 仓库: ${repo_path}"
        return 0
    else
        log "DEBUG" "非 Git 仓库: ${repo_path}"
        return 1
    fi
}

# 检查是否有未提交的修改
has_uncommitted_changes() {
    local repo_path="$1"
    
    cd "${repo_path}" || return 1
    if ! git diff --quiet --exit-code || ! git diff --cached --quiet --exit-code; then
        return 0  # 有未提交的修改
    else
        return 1  # 没有未提交的修改
    fi
}

# 获取当前分支名称
get_current_branch() {
    local repo_path="$1"
    
    cd "${repo_path}" || return 1
    git rev-parse --abbrev-ref HEAD 2>/dev/null
}

# 获取远程分支
get_remote_branch() {
    local repo_path="$1"
    
    if [[ ! -d "${repo_path}" ]]; then
        log "ERROR" "路径不存在: ${repo_path}"
        return 1
    fi
    
    if [[ ! -d "${repo_path}/.git" ]]; then
        log "ERROR" "非Git仓库: ${repo_path}"
        return 1
    fi
    
    cd "${repo_path}" || return 1
    
    # 首先尝试获取当前分支对应的远程分支
    local current_branch
    current_branch=$(get_current_branch "${repo_path}")
    if [[ -n "${current_branch}" ]]; then
        # 检查当前分支是否有对应的远程跟踪分支
        local remote_branch
        remote_branch=$(git rev-parse --abbrev-ref --symbolic-full-name "@{upstream}" 2>/dev/null || true)
        
        if [[ -n "${remote_branch}" ]]; then
            echo "${remote_branch}"
            log "INFO" "使用当前分支的远程跟踪分支: ${remote_branch}"
            return 0
        fi
    fi
    
    # 如果没有远程跟踪分支，检查常见的远程分支
    local remote_branches
    if ! remote_branches=$(git branch -r 2>/dev/null); then
        log "ERROR" "无法获取远程分支: ${repo_path}"
        return 1
    fi
    
    # 检查优先级：main分支 > master分支
    if echo "${remote_branches}" | grep -q "origin/main"; then
        echo "origin/main"
        log "INFO" "检测到远程分支: origin/main"
        return 0
    elif echo "${remote_branches}" | grep -q "origin/master"; then
        echo "origin/master"
        log "INFO" "检测到远程分支: origin/master"
        return 0
    else
        # 如果没有main/master，尝试使用第一个远程分支
        local first_remote_branch
        first_remote_branch=$(echo "${remote_branches}" | head -1 | sed 's/^[[:space:]]*//')
        if [[ -n "${first_remote_branch}" ]]; then
            echo "${first_remote_branch}"
            log "INFO" "使用第一个远程分支: ${first_remote_branch}"
            return 0
        else
            log "ERROR" "未检测到任何远程分支: ${repo_path}"
            return 1
        fi
    fi
}

# 执行 Git Pull
execute_git_pull() {
    local repo_path="$1"
    local dry_run="${2:-false}"
    local stash_changes="${3:-false}"
    local reset_hard="${4:-false}"
    
    if [[ ! -d "${repo_path}" ]]; then
        log "ERROR" "路径不存在: ${repo_path}"
        return 1
    fi
    
    cd "${repo_path}" || {
        log "ERROR" "无法进入目录: ${repo_path}"
        return 1
    }
    
    if [[ ! -d ".git" ]]; then
        log "ERROR" "非Git仓库: ${repo_path}"
        return 1
    fi
    
    local repo_name
    repo_name=$(basename "${repo_path}")
    
    # 获取远程分支
    local remote_branch
    if ! remote_branch=$(get_remote_branch "${repo_path}"); then
        log "ERROR" "无法获取远程分支: ${repo_name}"
        return 1
    fi
    
    # 提取远程名称和分支名称
    local remote_name branch_name
    if [[ "${remote_branch}" == *"/"* ]]; then
        remote_name="${remote_branch%%/*}"
        branch_name="${remote_branch#*/}"
    else
        remote_name="origin"
        branch_name="${remote_branch}"
    fi
    
    # 检查是否有未提交的修改
    local has_changes=false
    if has_uncommitted_changes "${repo_path}"; then
        has_changes=true
        log "WARN" "仓库 ${repo_name} 有未提交的修改"
        
        if [[ "${dry_run}" == "true" ]]; then
            log "INFO" "[干运行] 有未提交修改，将跳过拉取: ${repo_name}"
            return 1
        fi
        
        if [[ "${stash_changes}" == "true" ]]; then
            log "INFO" "自动贮藏本地修改: ${repo_name}"
            if git stash push -m "Auto-stash by git_pull_all script"; then
                log "INFO" "成功贮藏修改: ${repo_name}"
            else
                log "ERROR" "贮藏失败: ${repo_name}"
                return 1
            fi
        elif [[ "${reset_hard}" == "true" ]]; then
            log "WARN" "强制重置本地修改: ${repo_name}"
            if git reset --hard HEAD; then
                log "INFO" "成功重置修改: ${repo_name}"
            else
                log "ERROR" "重置失败: ${repo_name}"
                return 1
            fi
        else
            log "ERROR" "有未提交修改且未启用自动处理选项: ${repo_name}"
            log "INFO" "使用 --stash 自动贮藏或 --reset-hard 强制重置（谨慎！）"
            return 1
        fi
    fi
    
    if [[ "${dry_run}" == "true" ]]; then
        log "INFO" "[干运行] 将要执行: ${repo_name} -> git pull ${remote_name} ${branch_name}"
        return 0
    else
        log "INFO" "开始拉取: ${repo_name} -> git pull ${remote_name} ${branch_name}"
    fi
    
    # 执行 git pull
    if git pull "${remote_name}" "${branch_name}"; then
        log "INFO" "成功拉取: ${repo_name}"
        
        # 如果之前贮藏了修改，尝试重新应用
        if [[ "${has_changes}" == "true" && "${stash_changes}" == "true" ]]; then
            log "INFO" "尝试重新应用贮藏的修改: ${repo_name}"
            if git stash pop; then
                log "INFO" "成功重新应用贮藏的修改: ${repo_name}"
            else
                log "WARN" "重新应用贮藏时可能有冲突: ${repo_name}"
            fi
        fi
        
        return 0
    else
        log "ERROR" "拉取失败: ${repo_name}"
        return 1
    fi
}

# 生成统计报告
generate_statistics() {
    local -a repos=("$@")
    local total_count=${#repos[@]}
    local success_count=0
    local failure_count=0
    local skip_count=0
    
    log "INFO" "生成统计报告..."
    
    for repo in "${repos[@]}"; do
        local status_file="/tmp/git_pull_status_${repo##*/}.tmp"
        
        if [[ -f "${status_file}" ]]; then
            local status
            status=$(cat "${status_file}" 2>/dev/null)
            
            case "${status}" in
                "success")
                    success_count=$((success_count + 1))
                    ;;
                "failure")
                    failure_count=$((failure_count + 1))
                    ;;
                "skip")
                    skip_count=$((skip_count + 1))
                    ;;
                *)
                    skip_count=$((skip_count + 1))
                    ;;
            esac
            
            rm -f "${status_file}"
        else
            skip_count=$((skip_count + 1))
        fi
    done
    
    log "INFO" "========== 执行统计 =========="
    log "INFO" "总仓库数: ${total_count}"
    log "INFO" "成功数量: ${success_count}"
    log "INFO" "失败数量: ${failure_count}"
    log "INFO" "跳过数量: ${skip_count}"
    
    if [[ ${total_count} -gt 0 ]]; then
        local success_rate
        success_rate=$(echo "scale=1; ${success_count} * 100 / ${total_count}" | bc -l 2>/dev/null || echo "0")
        log "INFO" "成功率: ${success_rate}%"
    fi
    
    log "INFO" "============================"
    
    if [[ ${failure_count} -gt 0 ]]; then
        return 1
    else
        return 0
    fi
}

main "$@"
